 /*
 * Copyright (C) 2011 The Chromium OS Authors <chromium-os-dev@chromium.org>
 *
 * Device-Mapper block hash tree interface.
 *
 * This file is released under the GPL.
 */

#include <errno.h>
#include <string.h>

#include <asm/atomic.h>
#include <asm/page.h>
#include <linux/device-mapper.h>
#include <linux/dm-bht.h>
#include <linux/gfp.h>
#include <linux/scatterlist.h>

#define DM_MSG_PREFIX "dm bht"

/**
 * dm_bht_compute_hash: hashes a page of data
 */
static int dm_bht_compute_hash(struct dm_bht *bht, struct page *pg,
			       unsigned int offset, u8 *digest)
{
	struct hash_desc *hash_desc = &bht->hash_desc[0];
	struct scatterlist sg;

	sg_init_table(&sg, 1);
	sg_set_page(&sg, pg, PAGE_SIZE, offset);
	/* Note, this is synchronous. */
	if (crypto_hash_init(hash_desc)) {
	  DMCRIT("failed to reinitialize crypto hash");
		return -EINVAL;
	}
	if (crypto_hash_update(hash_desc, &sg, PAGE_SIZE)) {
		DMCRIT("crypto_hash_update failed");
		return -EINVAL;
	}
	if (bht->have_salt) {
		sg_set_buf(&sg, bht->salt, sizeof(bht->salt));
		if (crypto_hash_update(hash_desc, &sg, sizeof(bht->salt))) {
			DMCRIT("crypto_hash_update failed");
			return -EINVAL;
		}
	}
	if (crypto_hash_final(hash_desc, digest)) {
		DMCRIT("crypto_hash_final failed");
		return -EINVAL;
	}

	return 0;
}

void dm_bht_set_buffer(struct dm_bht *bht, void *buffer) {
	int depth;

	for (depth = 0; depth < bht->depth; ++depth) {
		struct dm_bht_level *level = dm_bht_get_level(bht, depth);
		struct dm_bht_entry *entry_end = level->entries + level->count;
		struct dm_bht_entry *entry;

		for (entry = level->entries; entry < entry_end; ++entry) {
			entry->nodes = buffer;
			memset(buffer, 0, PAGE_SIZE);
			buffer += PAGE_SIZE;
		}
	}
}

/**
 * dm_bht_compute - computes and updates all non-block-level hashes in a tree
 * @bht:	pointer to a dm_bht_create()d bht
 *
 * Returns 0 on success, >0 when data is pending, and <0 when a IO or other
 * error has occurred.
 *
 * Walks the tree and computes the hashes at each level from the
 * hashes below.
 */
int dm_bht_compute(struct dm_bht *bht)
{
	int depth, r = 0;

	for (depth = bht->depth - 2; depth >= 0; depth--) {
		struct dm_bht_level *level = dm_bht_get_level(bht, depth);
		struct dm_bht_level *child_level = level + 1;
		struct dm_bht_entry *entry = level->entries;
		struct dm_bht_entry *child = child_level->entries;
		unsigned int i, j;

		for (i = 0; i < level->count; i++, entry++) {
			unsigned int count = bht->node_count;

			memset(entry->nodes, 0, PAGE_SIZE);
			atomic_set(&entry->state, DM_BHT_ENTRY_READY);

			if (i == (level->count - 1))
				count = child_level->count % bht->node_count;
			if (count == 0)
				count = bht->node_count;
			for (j = 0; j < count; j++, child++) {
				struct page *pg = virt_to_page(child->nodes);
				u8 *digest = dm_bht_node(bht, entry, j);

				r = dm_bht_compute_hash(bht, pg, 0, digest);
				if (r) {
					DMERR("Failed to update (d=%d,i=%u)",
					      depth, i);
					goto out;
				}
			}
		}
	}
	r = dm_bht_compute_hash(bht,
				virt_to_page(bht->levels[0].entries->nodes),
				0, bht->root_digest);
	if (r)
		DMERR("Failed to update root hash");

out:
	return r;
}

/**
 * dm_bht_store_block - sets a given block's hash in the tree
 * @bht:	pointer to a dm_bht_create()d bht
 * @block:	numeric index of the block in the tree
 * @block_data:	array of u8s containing the block of data to hash
 *
 * Returns 0 on success.
 *
 * If the containing entry in the tree is unallocated, it will allocate memory
 * and mark the entry as ready.  All other block entries will be 0s.
 *
 * It is up to the users of the update interface to ensure the entry data is
 * fully populated prior to use. The number of updated entries is NOT tracked.
 */
int dm_bht_store_block(struct dm_bht *bht, unsigned int block,
		       u8 *block_data)
{
	int depth = bht->depth;
	struct dm_bht_entry *entry = dm_bht_get_entry(bht, depth - 1, block);
	u8 *node = dm_bht_get_node(bht, entry, depth, block);

	return dm_bht_compute_hash(bht, virt_to_page(block_data), 0, node);
}
